סינגלטון הוא שם למחלקה שאפשר ליצור רק העתק אחד שלה בכל זמן נתון. באמצעותה אפשר ליצור חיבור קבוע למסד הנתונים ולהפסיק להעביר עשרות פרמטרים בין פונקציות.
שים לב, מדריך זה מיועד להעשרת הידע שלך בלבד. אני ממליץ לא להשתמש בסיגלטונים במערכות שלך, למרות שהמדריך הזה מציג את הצדדים החיוביים של תבנית העיצוב. במקום זה אני מציעה ליצור מופע אחד של המחלקה בצורה ידנית ולהעביר אותו דרך הקונסטרקטור לכל המחלקות הדורשות חיבור למסד נתונים
יש לכם מחלקה לעבודה עם מסד (mysqli למשל). כדי להתחבר למסד אתם יוצרים מופע חדש של המחלקה ומשתמשים בו.
יש לכם פונקציה שמריצה שאילתה כלשהי. את המופע של המחלקה מעבירים לה כפרמטר?
יש לכם עוד 5 סקריפטים שמתחברים באינקלוד לסקריפט הנוכחי. חלק מהם מתחברים רק בתנאים מסוימים וחלקם צריכים חיבור למסד כדי לעבוד. המופע של mysqli נוצר בהם ונדרס שוב בקוד של העמוד הנוכחי? נוצרים כמה חיבורים למסד?
singlton הוא שם למבנה של מחלקה, שרק מופע אחד שלה יכול להיות קיים במקביל. כדי ללמוד איך לשמור על חיבור יחיד, עובד וזמין למסד בכל מקום בקוד בעזרת siglton למסד — המשיכו לקרוא
Singleton
סינגלטון היא שם למחלקה שאפשר ליצור רק מופע אחד ממנה. אם תנסו ליצור שני מופעים של המחלקה לא תקבלו שגיאה, פשוט תקבלו את אותו אובייקט בזיכרון פעמיים.
ניצור את המחלקה לניסויים קליניים שלנו, המחלקה dog
/** Sample dog class */
class dog
{
/** Creates new dog's instance */
public function __construct() { echo 'aww aww <br/>'; }
}
$chappi = new dog();
$rex = new dog();
class dog
{
/** Creates new dog's instance */
public function __construct() { echo 'aww aww <br/>'; }
}
$chappi = new dog();
$rex = new dog();
נוצרו שני מופעים של המחלקה dog. כל מופע בהיווצרותו הדפיס למסך aww aww.
משתנה סטטי
משתנה סטטי הוא משתנה המשותף לכל המופעים של המחלקה. לכל מחלקה יש את המאפיינים שלה (property). לכלב יכול להיות מאפיין "שם" או "גיל". כל מופע שומר את הערכים של המאפיינים שלו בתאים משלו בזיכרון.
משתנה סטטי הוא משתנה שנשמר בתא מסוים בזיכרון וכל המופעים של אותה מחלקה פונים אל אותו תא. מן תא משותף כזה לכל המופעים.
/** Sample dog class */
class dog
{
public static $name = 'bob';
/** Creates new dog's instance */
public function __construct() { echo 'aww aww <br/>'; }
/** Changes dog's name
* @param string $name new dog's name */
public function change_name($name) { self::$name = $name; }
/** Output's dogs name */
public function say_name() { echo 'I am ', self::$name ,'<br/>'; }
}
$chappi = new dog();
$rex = new dog();
$chappi->change_name('chappi');
$chappi->say_name();
$rex->say_name();
class dog
{
public static $name = 'bob';
/** Creates new dog's instance */
public function __construct() { echo 'aww aww <br/>'; }
/** Changes dog's name
* @param string $name new dog's name */
public function change_name($name) { self::$name = $name; }
/** Output's dogs name */
public function say_name() { echo 'I am ', self::$name ,'<br/>'; }
}
$chappi = new dog();
$rex = new dog();
$chappi->change_name('chappi');
$chappi->say_name();
$rex->say_name();
הקוד מחזיר
aww aww
aww aww
I am chappi
I am chappi
תחילה נוצרים שני מופעים של המחלקה.
שניהם משתמשים באותו תא בזיכרון כדי לשמור את המשתנה $name
ברגע שאחד מהם משנה לעצמו את השם, הוא מעדכן את המשתנה המשותף ושני המופעים
רואים את אותו ערך.
נשתמש בתא הזה כדי לרשום לשם האם כבר נוצר מופע של המחלקה או לא. כל המופעים החדשים של המחלקה לפני ההיווצרות יבדקו האם כבר קיים מופע כזה בזיכרון באמצעות המשתנה המשותף ואם כן — יחזירו מופע קיים, במקום ליצור אחד חדש.
/** Sample dog class */
class dog
{
private static $the_dog = null;
/** Creates new dog's instance */
private function __construct() { echo 'NEW DOG created <br/>'; }
/** Prevents creating an object by cloning */
private function __clone() { }
/** Returns singlton instance of a dog */
public static function get_dog()
{
/*
נבדוק האם יש משהו במשתנה המשותף
אם אין — ניצור מופע חדש ונכניס אותו למשתנה המשותף
כדי שהבאים אחרי יוכלו לבדוק את המשתנה הזה ולדעת האם
כבר קיים מופע של המחלקה, או צריך ליצור אחד חדש
*/
if( self::$the_dog === null)
{
self::$the_dog = new dog();
}
return self::$the_dog;
}
}
$rex = dog::get_dog();
$chompie = dog::get_dog();
$bob = dog::get_dog();
class dog
{
private static $the_dog = null;
/** Creates new dog's instance */
private function __construct() { echo 'NEW DOG created <br/>'; }
/** Prevents creating an object by cloning */
private function __clone() { }
/** Returns singlton instance of a dog */
public static function get_dog()
{
/*
נבדוק האם יש משהו במשתנה המשותף
אם אין — ניצור מופע חדש ונכניס אותו למשתנה המשותף
כדי שהבאים אחרי יוכלו לבדוק את המשתנה הזה ולדעת האם
כבר קיים מופע של המחלקה, או צריך ליצור אחד חדש
*/
if( self::$the_dog === null)
{
self::$the_dog = new dog();
}
return self::$the_dog;
}
}
$rex = dog::get_dog();
$chompie = dog::get_dog();
$bob = dog::get_dog();
רק NEW DOG created אחד יופיע על המסך.
כל השלושה הם אותו מופע של כלב.
חיבור עם מחלקה של מסד
את הבסיס למחלקה לקחתי מהכתבה שיפור ביצועי מסד עם mysqli
/** Mysqli Wrapper */
class db extends mysqli
{
private $connected = false;
private static $instance = null;
/** Returns singleton instance */
public static function get_instance()
{
if (self::$instance === null)
{
self::$instance = new self('localhost', 'user', 'pass', 'base1');
}
return self::$instance;
}
/**
* Performs MySQLI database connection
* @param string $host the machine/host the database is located at
* @param string $user database user
* @param string $pass password for database user
* @param string $db database's name
* @param string $charset connection collation charset
*/
private function __construct($host, $user, $pass, $db, $charset = 'utf8')
{
parent::__construct($host, $user, $pass, $db);
if ($this->connect_error) die("Connection Error: ".$this->connect_error );
$this->set_charset($charset);
$this->connected = true;
}
/** Prevents creating an object by cloning */
private function __clone() { }
/**
* Executes sql query against the database and handles failures
* @param string $query the SQL to execute
* @param int $resultmode MYSQLI_STORE_RESULT or MYSQLI_USE_RESULT
* @return resource result to iterate or dies on error
*/
public function query($query, $resultmode = MYSQLI_STORE_RESULT )
{
$res = parent::query($query, $resultmode);
if($res === false) die("SQL Error: ".$this->error );
return $res;
}
/** Closes the connection to the database */
function __destruct() { if($this->connected) $this->close(); }
}
class db extends mysqli
{
private $connected = false;
private static $instance = null;
/** Returns singleton instance */
public static function get_instance()
{
if (self::$instance === null)
{
self::$instance = new self('localhost', 'user', 'pass', 'base1');
}
return self::$instance;
}
/**
* Performs MySQLI database connection
* @param string $host the machine/host the database is located at
* @param string $user database user
* @param string $pass password for database user
* @param string $db database's name
* @param string $charset connection collation charset
*/
private function __construct($host, $user, $pass, $db, $charset = 'utf8')
{
parent::__construct($host, $user, $pass, $db);
if ($this->connect_error) die("Connection Error: ".$this->connect_error );
$this->set_charset($charset);
$this->connected = true;
}
/** Prevents creating an object by cloning */
private function __clone() { }
/**
* Executes sql query against the database and handles failures
* @param string $query the SQL to execute
* @param int $resultmode MYSQLI_STORE_RESULT or MYSQLI_USE_RESULT
* @return resource result to iterate or dies on error
*/
public function query($query, $resultmode = MYSQLI_STORE_RESULT )
{
$res = parent::query($query, $resultmode);
if($res === false) die("SQL Error: ".$this->error );
return $res;
}
/** Closes the connection to the database */
function __destruct() { if($this->connected) $this->close(); }
}
השימוש
$db1 = db::get_instance();
$db2 = db::get_instance();
function do_something()
{
$db3 = db::get_instance();
$db3->query('SELECT `something` FROM `do`');
}
$db2 = db::get_instance();
function do_something()
{
$db3 = db::get_instance();
$db3->query('SELECT `something` FROM `do`');
}
למחלקה נוסף משתנה סטטי ששומר מופע אחד של המחלקה. כל פעם שנבקש לקבל מופע חדש בעזרת הפונקציה get_instance() היא תבדוק האם מופע כבר קיים ואם כן - תחזיר את המופע הקיים. אם לא - תיצור מופע חדש ותחזיר אותו.
עכשיו אין שום בעיה פעם אחת להוסיף את הקובץ db.php באינקלוד בטעינה ולהשתמש בחיבור הקיים למסד (שיוצר רק כשישתמשו בו בפעם הראשונה)
תגובות לכתבה:
כשעושים ככה:
public function __construct
עדיין אפשר לעשות יותר מעצם אחד, כשפשוט מפעילים את הבנאי מחוץ למחלקה. מקובל להפוך את הבנאי ל private, כדי שרק פונקציה פנימית תוכל ליצור עצם, וככה בטוח לא יהיה יותר מעצם אחד.
תודה, הכתבה עודכנה :-)
לא הבנתי מה הבדל שגורם לזה להבצר פעם אחת
אחלה הסבר, תודה!
שתי שאלות
1. אפשר היה לוותר על connected?, יכולה להיות בעיה במקום להשתמש ב-connected פשוט לבדוק את הערך של Instance? במידה ושונה מ-null אני יודע שיש חיבור..?
2. במידה והייתי מנסה ליצור כל פעם אובייקט חדש (עם new), אבל בקונסטרקטור הייתי בודק אם כבר קיים ערך ל-instance, זה אפשרי? (כי instance אמור להיות זהה לכל המופעים)
תודה!
1. במקרה הזה אתה לא תדע אם יש לך חיבור פתוח למסד נתונים שצריך לסגור בסוף הסקריפט או לא. אם החיבור למסד בפועל לא הצליח, אבל אתה בכל זאת תנסה לעשות coonection->close בסוף ה destructor אתה תקבל עוד שגיאה.
2. אי אפשר להחזיר ערכים מתוך קונסטרקטור, ככה שגם אם תבדוק שם כל מה שאתה רוצה, הערך שיוחזר מהקונסטרקטור יהיה מופע חדש של המחלקה ולא משנה מה תבדוק שם אות תנסה להחזיר.
תודה רבה!
שכחת להגדיר את get_instance כסטטית.
צודק. טופל, תודה :)
זה בונה על כך שעושים unset למופעים של המחלקה לפני שיוצרים חיבור חדש למסד אחר וכדו', כן?
כלומר, אי אפשר פשוט לגשת למתודה close של המופעים של המחלקה כדי לאפס את החיבור ושיהיה אפשר ליצור חיבור חדש?
תוסיף למחלקה מטודה משלך בשם reset שתסגור את החיבור הקיים ותפתח אחד חדש
אם אתה ממש בטוח שיש סיבה לעשות את זה, כי אני לא
לא, זה בסדר. פשוט לקח לי זמן לקלוט שצריך להשמיד את המופעים של המחלקה, ולא רק לגשת ל-close. :)
אני מעדיף לעשות את זה ככה:
http://phpguide.co.il/phplive?code=561
במקום כל פעם לקרוא לgetInstance לתוך משתנה ולעבוד דרכו, אפשר פשוט לקרוא לפונקציה (נגיד query) למשתנה (יש מקרים שלא צריך אפילו, כמו UPDATE \ DELETE וכו'..)
יענו:
DB::query("DELETE FROM `teble` WHERE 1;");
במקום:
$db = db::get_instance();
$db->query("DELETE FROM `teble` WHERE 1;");
וזהו :D
@ldbrgr
+1
כשעברתי ל-php 5.3 עברתי לצורה הזו.
אמנם query סטטי יותר נחמד, אבל זה אומר שהקוד שלך לא ניתן לבדיקה אוטומטית עם טסטים.
בכלל שום דבר סטטי לא ניתן ליוניט טסטינג וגורם לעוד יותר בעיות עם מישהו ירצה להרחיב את המחלקה שלך בעתיד. עדיך לכתוב עוד פעם getInstance.
לא מזמן חשבתי איך לתקן את כל הבעיות של PHP ואחד הדברים שעלו לי זה שצריך להעיף מהשפה בכלל את האפשרות לעשות דברים סטטים. לגמרי. להשאיר trait אחד של סינגלטון מובנה על בתוך השפה ולהוציא את הקונספט של הסטטי.
אמנם קוד סטטי חוסך קצת זיכרון למערכת, אבל זיכרון זה לא הבעיה הגדולה של מערכות היום. קוד גרוע, לאומת זאת, זה בהחלט בעיה.